package modeler.shape;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Vector;

import javax.vecmath.Matrix4f;

import modeler.SceneNode;

import javax.media.opengl.GL;
import javax.media.opengl.glu.*;

/**
 * @author ags
 */
public abstract class Shape extends SceneNode {

  // Global error tolerance determines the subdivision level of all objects
  public transient static float TOLERANCE = 0.01f;
  
  // Auto-incrementing id for picking purposes.
  private static int nextId = 1000;
  private int pickId = getNextPickId(this);

  // A map from pickIds to Shape objects
  private static Vector<Shape> pickableShapes = new Vector<Shape>();

  // The default material parameters
  protected final float[] diffuseColor = new float[] {0.8f, 0.2f, 0.2f, 1.0f};
  protected final float[] specularColor = new float[] {0.0f, 0, 0, 1.0f};
  protected float expSpecular = 40.0f;

  //The mesh for this shape
  public transient Mesh mesh;
  
  /**
   * Basic constructor uses default material and builds the mesh.
   *
   */
  public Shape() {

    String classname = this.getClass().getName();
    setName(classname.substring(classname.lastIndexOf('.') + 1, classname.length()));
    
  }

  /**
   * Builds a mesh for this shape using the current subdivision levels.
   */
  public abstract void buildMesh();
  
  /**
   * Returns the next available pick ID
   * @param s
   * @return
   */
  private static int getNextPickId(Shape s) {

    // the index of s in pickableShapes is (s.pickId - 1000)
    pickableShapes.add(s);
    return nextId++;
    
  }

  /**
   * Gets the shape with the given ID
   * @param id
   * @return
   */
  public static Shape get(int id) {

    int idx = id - 1000;
    if (idx < 0 || idx >= pickableShapes.size())
      return null;
    
    return pickableShapes.get(idx);
    
  }

  /**
   * Returns the pick id for this object
   * @return
   */
  public int getPickId() {

    return pickId;
  }
  
  /**
   * @see modeler.SceneNode#render(javax.media.opengl.GL)
   */
  public int render(GL gl, GLU glu) {
	  // Rebuild the mesh if the refinement changed
	  if(mesh == null)
		  buildMesh();

	  if(mesh != null) {
		  // Set the pick id
		  gl.glLoadName(pickId);

		  // TODO - fill in code to set up the material parameters

		  mesh.render(gl);
		  return mesh.numTriangles;
	  }
	  else {
		  return 0;
	  }
  }

  /**
   * @see javax.swing.tree.TreeNode#getAllowsChildren()
   */
  public boolean getAllowsChildren() {

    return false;

  }

  /**
   * @see modeler.SceneNode#readData(java.io.ObjectInputStream)
   */
  protected void readData(ObjectInputStream in) throws IOException {

    pickId = in.readInt();
    
    //Update the pickId maps
    if(nextId <= pickId)
      nextId = pickId + 1;
    if(pickableShapes.size() < (pickId - 999))
      pickableShapes.setSize(pickId - 999);
    pickableShapes.set(pickId-1000, this);
    
    diffuseColor[0] = in.readFloat();
    diffuseColor[1] = in.readFloat();
    diffuseColor[2] = in.readFloat();
    specularColor[0] = in.readFloat();
    specularColor[1] = in.readFloat();
    specularColor[2] = in.readFloat();
    expSpecular = in.readFloat();
  }

  /**
   * @see modeler.SceneNode#saveData(java.io.ObjectOutputStream)
   */
  protected void saveData(ObjectOutputStream out) throws IOException {

    out.writeInt(pickId);
    out.writeFloat(diffuseColor[0]);
    out.writeFloat(diffuseColor[1]);
    out.writeFloat(diffuseColor[2]);
    out.writeFloat(specularColor[0]);
    out.writeFloat(specularColor[1]);
    out.writeFloat(specularColor[2]);
    out.writeFloat(expSpecular);
    
  }

  /**
   * @see modeler.SceneNode#exportMainData(java.util.HashMap, java.io.PrintWriter)
   */
  protected void exportMainData(HashMap materialsMap, PrintWriter pw) {

    MaterialWrapper mat = new MaterialWrapper(this);
    
    //Check and see if our material is already in the map
    if(materialsMap.containsKey(mat))
      return;
    
    //Add ourselves to the materials map
    String name = "Material"+materialsMap.size();
    materialsMap.put(mat, name);
    
    //Write our material out
    pw.println("  <material name=\""+name+"\" type=\"ray1.material.Phong\">");
    pw.println("    <diffuseColor>"+diffuseColor[0]+" "+diffuseColor[1]+" "+diffuseColor[2]+"</diffuseColor>");
    pw.println("    <specularColor>"+specularColor[0]+" "+specularColor[1]+" "+specularColor[2]+"</specularColor>");
    pw.println("    <exponent>"+expSpecular+"</exponent>");
    pw.println("  </material>");
  }
  
  /**
   * @see modeler.SceneNode#exportMeshData(java.util.HashMap, java.io.PrintWriter, javax.vecmath.Matrix4f)
   */
  protected void exportMeshData(HashMap materialsMap, PrintWriter pw, Matrix4f mat) {

    String matName = (String) materialsMap.get(new MaterialWrapper(this));
    pw.println(matName);
    if(mesh == null)
      buildMesh();
    mesh.writeMesh(mat, pw);
    
  }
  
  /**
   * @see javax.swing.tree.TreeNode#isLeaf()
   */
  public boolean isLeaf() {

    return true;

  }

  /**
   * Quick casting version of cosine
   * @param d
   * @return
   */
  protected float cos(double d) {

    return (float) Math.cos(d);
  }
  
  /**
   * Quick casting version of sine
   * @param d
   * @return
   */
  protected float sin(double d) {

    return (float) Math.sin(d);
  }
  
  /**
   * Used to test the equality of the material part of this shape
   * @author arbree
   * Oct 23, 2005
   * Shape.java
   * Copyright 2005 Program of Computer Graphics, Cornell University
   */
  private class MaterialWrapper {
    
    /**
     * The shape this wraps
     */
    protected final Shape shape;
    
    /**
     * Wraps the given shape
     */
    public MaterialWrapper(Shape inShape) {
      
      shape = inShape;
      
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {

      if(!(obj instanceof MaterialWrapper))
        return false;
      
      Shape oShape = ((MaterialWrapper) obj).shape;
      
      if(oShape.expSpecular != shape.expSpecular)
        return false;
      
      for(int i = 0; i < 3; i++)      
        if(oShape.diffuseColor[i] != shape.diffuseColor[i] || oShape.specularColor[i] != shape.specularColor[i])
          return false;
      
      return true;
      
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {

      int outCode = 0;
      float total = shape.expSpecular;
      for(int i = 0; i < 3; i++)
        total += shape.diffuseColor[i]+shape.specularColor[i];
      total *= 10000;
      outCode += (int) total;
      return outCode;
      
    }
  }

  public float[] getDiffuseColor() {
	  return diffuseColor;
  }

public float getExpSpecular() {
	return expSpecular;
}

public void setExpSpecular(float expSpecular) {
	this.expSpecular = expSpecular;
}

public float[] getSpecularColor() {
	return specularColor;
}

}